﻿using System;
using System.Linq;
using RayDen.Library.Components.SystemComponents;
using RayDen.Library.Core;
using RayDen.RayEngine.Core.Interface;
using RayDen.RayEngine.Core.Types;

namespace RayDen.RayEngine.Samplers
{
    public class LDSampler : ISampler
    {
        public static int MagicValue1 = 64;
        public static int MagicValue2 = 128;
        public static int SuperSampling = 4;

        public int Width { get; protected set; }
        public int Height { get; protected set; }

        public int screenWidth, screenHeight, screenStartLine, samplePerPixel, samplePerPixel2;
        int currentSampleScreenX, currentSampleScreenY, currentSubSampleIndex, currentSample;
        public int pass;
        public bool lowLatency, previewOver;
        public int valuesPerSample;

        private IQuasiMonteCarloSequence ldSequence;

        public int tIdx;
        private Sample[] samples;
        private IRandomProvider rnd;
        public LDSampler()
        {
            rnd = new HaltonSequence();
            ldSequence =

                //new HaltonSequence();
            new HaltonSequence();
            //  new FastRandom();
            //new BaseQMCSequence();
        }
        public float GetLDValue(Sample sample)
        {
            return ldSequence.NextValue(sample.Index++);
            //return (float)ldSequence.NextDouble();
            //return rnd.NextFloat();
            //return halton.NextFloat();
        }

        public void Reinitialise(int seed)
        {
            this.ldSequence.Reinitialise(seed);
            this.rnd.Reinitialise(seed);
        }

        public float NextFloat()
        {
            return ldSequence.NextFloat();
        }

        public double NextDouble()
        {
            return ldSequence.NextDouble();
        }

        public Sample GetSample(Sample prevSample)
        {
            Sample sm;
            sm =  GetNextSample1x1(prevSample);
            if (currentSample == SuperSampling * screenWidth * screenHeight - 1)
            {
                pass += SuperSampling * SuperSampling;
                currentSample = 0;
            }
            TotalSamples++;
            //sm.Index = (int)(rnd.NextFloat()*4096);

            return sm;
        }

        public float GetLazyValue(Sample sample)
        {
            return ldSequence.NextValue(sample.Index++);
        }
        public float GetLazyValue(Sample sample, int index)
        {
            return ldSequence.NextValue(index);
        }


     
        public void Init(int width, int height)
        {
            currentSubSampleIndex = 0;
            pass = 0;
            previewOver = !lowLatency;
            samplePerPixel = 4;
            samplePerPixel2 = 4;
            if (screenWidth == 0 && screenHeight == 0)
            {
                Width = screenWidth = width;
                Height = screenHeight = height;
                screenWidth = width - 1;
                screenHeight = height - 1;
            }
            screenStartLine = Math.Max(0, screenStartLine - 1);
            currentSubSampleIndex = 0;
            currentSampleScreenY = screenStartLine;
            TotalSamples = 0;
            Tracer.TraceLine("Generating samples...");
            //samples = Enumerable.Range(0, SuperSampling*screenWidth * screenHeight - 1).Select(i => GetNextSample1x1(null)).ToArray();
            pass = 0;
            Tracer.TraceLine("Samples generated.");

            //startTime = WallClockTime(); 
        }

        public int GetPass()
        {
            return pass;
        }

        public long TotalSamples { get; set; }
      
        Sample GetNextSampleNxN(Sample oldSample)
        {
            int stepX = currentSubSampleIndex % samplePerPixel;
            int stepY = currentSubSampleIndex / samplePerPixel;

            int scrX = currentSampleScreenX;
            int scrY = currentSampleScreenY;

            currentSubSampleIndex++;
            if (currentSubSampleIndex == samplePerPixel2)
            {
                currentSubSampleIndex = 0;
                currentSampleScreenX++;
                if (currentSampleScreenX >= screenWidth)
                {
                    currentSampleScreenX = 0;
                    currentSampleScreenY++;

                    if (currentSampleScreenY >= screenHeight)
                    {
                        currentSampleScreenY = 0;
                        pass += samplePerPixel2;
                    }
                }
            }
            Sample sample = oldSample ?? new Sample(this) { Index = (int)this.sampleIndex++ };

            float r1 = (stepX + GetLDValue(sample)) / samplePerPixel - .5f;
            float r2 = (stepY + GetLDValue(sample)) / samplePerPixel - .5f;
            sample.imageX = scrX + r1;
            sample.imageY = scrY + r2;
            sample.pass = pass;

            return sample;

        }

        Sample GetNextSample1x1(Sample oldSample)
        {
            int scrX = currentSampleScreenX;
            int scrY = currentSampleScreenY;

            currentSampleScreenX++;
            if (currentSampleScreenX >= screenWidth)
            {
                currentSampleScreenX = 0;
                currentSampleScreenY++;

                if (currentSampleScreenY >= screenHeight)
                {
                    currentSampleScreenY = 0;
                    pass++;
                }
            }

            float r1 = ldSequence.NextFloat() - .5f;
            float r2 = ldSequence.NextFloat() - .5f;
            Sample sample = oldSample ?? new Sample(this) { Index = (int)this.sampleIndex++ };
            sample.imageX = scrX + r1;
            sample.imageY = scrY + r2;
            sample.pass = pass;
            return sample;
        }

        private long sampleIndex = 1;
        Sample GetNextSamplePreview()
        {
            int scrX, scrY;
            for (; ; )
            {
                int stepX = pass % 4;
                int stepY = (pass / 4) % 4;

                scrX = currentSampleScreenX * 4 + stepX;
                scrY = currentSampleScreenY * 4 + stepY;

                currentSampleScreenX++;
                if (currentSampleScreenX * 4 >= screenWidth)
                {
                    currentSampleScreenX = 0;
                    currentSampleScreenY++;

                    if (currentSampleScreenY * 4 >= screenHeight)
                    {
                        currentSampleScreenY = 0;
                        pass++;
                    }
                }

                // Check if we are inside the screen
                if ((scrX < screenWidth) && (scrY < screenHeight))
                {
                    // Ok, it is a valid sample
                    break;
                }
                else if (pass >= MagicValue1)
                {
                    return GetSample(null);
                }
            }
            float r1 = ldSequence.NextFloat() - .5f;
            float r2 = ldSequence.NextFloat() - .5f;

            var sample = new Sample(this) { imageX = scrX + r1, imageY = scrY + r2, pass = pass,  Index = (int)this.sampleIndex++  };
            return sample;
        }
    }
}